14.3 キャンセレーション
コンテキストにはアプリケーションを中断したり、複数のゴルーチンを制御したりもできる
複数のゴルーチンが起動し、どれか一つがエラーを起こしたら場合に処理を中止することをキャンセレーション(cancellation)と呼ぶ
キャンセル可能なコンテキストを作成するにはcontext.WithCancelやcontext.WithCancelCauseを使用する
キャンセル時の原因を知りたい場合はcontext.WithCancelCauseを使用し、context.Causeでキャンセレーションエラーを取得する
context.CauseはGo 1.20で追加された関数で、後方互換性を保つためにcontext.Contextのメソッドにはならなかった
code:go
package main
import (
"context"
"errors"
"fmt"
"net/http"
"sync"
"time"
)
func makeRequest(ctx context.Context, url string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
return http.DefaultClient.Do(req)
}
func main() {
ctx, cancelFunc := context.WithCancelCause(context.Background())
defer cancelFunc(nil) // main終了前にキャンセル用ゴルーチンを解放する(nil=正常終了)。解放しないとメモリリークが発生する
ch := make(chan string)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for {
resp, err := makeRequest(ctx, "http://httpbin.org/status/200,200,200,500")
if err != nil {
cancelFunc(fmt.Errorf("in status goroutine: %w", err))
return
}
if resp.StatusCode == http.StatusInternalServerError {
cancelFunc(errors.New("bad status"))
return
}
ch <- "success from status"
time.Sleep(1 * time.Second)
}
}()
go func() {
defer wg.Done()
for {
resp, err := makeRequest(ctx, "http://httpbin.org/delay/1")
if err != nil {
fmt.Println("in delay goroutine:", err)
cancelFunc(fmt.Errorf("in delay goroutine: %w", err))
return
}
ch <- "success from delay: " + resp.Header.Get("date")
}
}()
loop:
for {
select {
case s := <-ch:
fmt.Println("in main:", s)
case <-ctx.Done(): // 最初に呼ばれたcancelFunc(err)が保持され、後続のキャンセルでは上書きされない
fmt.Println("in main: cancelled with error", context.Cause(ctx)) // キャンセレーションエラーが出力される
break loop
}
}
wg.Wait()
fmt.Println("context cause:", context.Cause(ctx)) // キャンセレーションエラーが出力される
}